package gui;

import data.UserPreferences;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import kit.Component;
import midi.MidiController;
import midi.MidiSubject;
import tablature.BarLine;
import tablature.Measure;
import tablature.TimeSignature;

/**
 *
 * @author eric
 */
public class TablatureWindow extends javax.swing.JPanel
{



    private TimeSignature timeSignature;
    private int tempo;

    private int numMeasuresShown;

    // The document with the tablature.
    private DefaultStyledDocument document;


    // Plain and Bold styles;
    private Style PLAIN;
    private Style BOLD;


    private ArrayList <Component> components;
    private ArrayList <Measure> measures;

    private Measure curMeasure;

    private int beatNum;
    private int measureNum;

    private long lastBeatTime;
    
    private static TablatureWindow instance;

    private MidiSubject midiController;
    
    private int timeLength;

    

    /**
     * Gets a singleton instance of this class.
     * @return a singleton instance of this class.
     */
    public static TablatureWindow getInstance()
    {
        if (instance == null)
        {
            instance = new TablatureWindow();
        }
        return instance;
    }





    private TablatureWindow()
    {
        StyleContext context = new StyleContext();
        document = new DefaultStyledDocument(context);

        PLAIN = document.addStyle("PLAIN", null);

        StyleConstants.setFontFamily(PLAIN, "Monospaced");
        StyleConstants.setFontSize(PLAIN, 16);

        BOLD = document.addStyle("BOLD", PLAIN);
        StyleConstants.setBold(BOLD, true);


        initComponents();



        
    }


    public JPanel getMainComponent()
    {
        return this;
    }

    /**
     * Registers or unregisters
     * all the components to listen to MIDI device.
     * @param listen - True to register components, False to unregister.
     */
    public void registerComponents(boolean listen)
    {
        components = UserPreferences.getInstance().getComponents();

        midiController.refreshDevice();
        for (Component c : components)
        {
            if (listen)
            {
                midiController.registerObserver(c);
            }
            else
            {
                midiController.removeObserver(c);
            }
        }
    }


    /**
     * This is called whenever a component is hit.
     * The component itself is the one that actually calls the method.
     * @param componentShortName - the components shortName (S, HH, etc.).
     * @param hitNote - the note that should be put it for this
     * current note (x, o, etc.).
     */
    public void hit(String componentShortName, char hitNote)
    {
        int curBeat = getBeatNum();

        long hitTime = System.currentTimeMillis();
        long lastTime = getLastBeatTime();

        long diff = hitTime - lastTime;

        int note = 0;

        long low = 0;
        long high = timeLength;

        for (int i = 0; i < timeSignature.getBeatSize(); i++)
        {
            if (diff >= low && diff < high)
            {
                note = i;
                break;
            }
            else
            {
                low = high;
                high = high + timeLength;
            }
        }

        note = note + ((curBeat - 1) * timeSignature.getBeatsPerMeasure());

        curMeasure = measures.get(measureNum);

        int barLineNum = curMeasure.getBarLinePos(componentShortName);
        BarLine bl = curMeasure.getBarLine(barLineNum);
        if (note < 16)
        {
            bl.setNote(note, hitNote);
        }
        curMeasure.setBarLine(barLineNum, bl);
        measures.set(measureNum, curMeasure);
        setText(curMeasure.toString());

    }

    public void beat()
    {
        setLastBeatTime(System.currentTimeMillis());

        setBeatNum(getBeatNum() + 1);

        curMeasure = measures.get(measureNum);

        BarLine bl = curMeasure.getTimingBar();

        

        if (getBeatNum() > 4)
        {
            measureNum++;
            curMeasure = constructDefaultMeasure();
            measures.add(curMeasure);
            setText(measures.get(measureNum).toString());
            setBeatNum(1);

            bl = new BarLine("", curMeasure.getTimeSignature());
        }
        if (getBeatNum() > 1)
        {
            int pos = (getBeatNum() - 2) * timeSignature.getBeatsPerMeasure();
            char rep = timeSignature.getTimingLine().charAt(pos);
            bl.setNote(pos, rep);
        }

        bl.setNote((getBeatNum() - 1) * timeSignature.getBeatsPerMeasure(), 'X');
        curMeasure.setTimingBar(bl);

        measures.set(measureNum, curMeasure);


        

        setText(measures.get(measureNum).toString());
    }

    /**
     * Sets the time signature for the tablature.
     * @param timeSignature - the new time signature.
     */
    public void setTimeSignature(TimeSignature timeSignature)
    {
        this.timeSignature = timeSignature;
    }

    /**
     * Sets the tempo for the tablature.
     * @param tempo - the new tempo.
     */
    public void setTempo(int tempo)
    {
        this.tempo = tempo;
        timeLength = tempo * 1000 / 60 / timeSignature.getBeatSize();
    }

    /**
     * Returns all the measures that were played.
     * @return all the measures.
     */
    public ArrayList getAllMeasures()
    {
        return measures;
    }



    public String getText(int pos, int length)
    {
        String text = "";
        try
        {
            text = document.getText(pos, length);
        }
        catch (BadLocationException ex)
        {
            Logger.getLogger(TablatureWindow.class.getName()).log(Level.SEVERE, null, ex);
        }
        return text;
    }

    /**
     * Replaces the current text from some range.
     * @param pos
     * @param length
     * @param rep
     * @param style
     */
    public void replaceText(int pos, int length, String rep, Style style)
    {
        try
        {
            document.replace(pos, length, rep, style);
        }
        catch (BadLocationException ex)
        {
            Logger.getLogger(TablatureWindow.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Inserts text at the given position.
     * @param str - the text to be added to the document
     * @param pos - the position to add the text.
     */
    public void insertText(int pos, String str, Style style)
    {
        try
        {
            document.insertString(pos, str, style);
            tabTextPane.setCaretPosition(document.getLength());
        }
        catch (BadLocationException ex)
        {
            System.out.println("Could not insert text.");
            System.out.println(ex);
        }
    }
    

    /**
     * Replaces the document's text with the given text.
     * @param str - the text to replace the document with.
     */
    public void setText(String str)
    {

        clearText();
        insertText(document.getLength(), str, PLAIN);
    }

    /**
     * Clears the document's text.
     */
    public void clearText()
    {
        try
        {
            document.remove(0, document.getLength());
        }
        catch (BadLocationException ex)
        {
            System.out.println("Could not clear text");
            System.out.println(ex);
        }
    }


    /**
     * Makes the default measure to be added each time we need
     * to add a new one.
     * @return
     */
    private Measure constructDefaultMeasure()
    {

        // Construct blank measure.
        Measure measure = new Measure(timeSignature);

        for (Component component : components)
        {
            // Make a new barline with this component and the given
            // time signature.
            // Then add it to the current measure.
            BarLine barLine = new BarLine(component.getShortName(), timeSignature);
            measure.addBarLine(component.getShortName(), barLine);
        }

        BarLine barLine = new BarLine("", timeSignature);
        measure.addBarLine("", barLine);

        return measure;
    }

    public synchronized void setLastBeatTime(long time)
    {
        lastBeatTime = time;
    }

    public synchronized long getLastBeatTime()
    {
        return lastBeatTime;
    }

    public synchronized int getBeatNum()
    {
        return beatNum;
    }

    public synchronized void setBeatNum(int beatNum)
    {
        this.beatNum = beatNum;
    }


    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        tabScrollPane = new javax.swing.JScrollPane();
        tabTextPane = new javax.swing.JTextPane();

        tabTextPane.setDocument(document);
        tabTextPane.setEditable(false);
        tabTextPane.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N
        tabTextPane.setCaretPosition(document.getLength());
        tabScrollPane.setViewportView(tabTextPane);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(tabScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 403, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(tabScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 364, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JScrollPane tabScrollPane;
    private javax.swing.JTextPane tabTextPane;
    // End of variables declaration//GEN-END:variables

}
